/*
 * Copyright (C) 2015 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.mingxun.project.http;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.EOFException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

import okhttp3.Connection;
import okhttp3.FormBody;
import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSource;


/**
 * An OkHttp interceptor which logs request and response information. Can be applied as an
 * {@linkplain OkHttpClient#interceptors() application interceptor} or as a {@linkplain
 * OkHttpClient#networkInterceptors() network interceptor}. <p> The format of the logs created by
 * this class should not be considered stable and may change slightly between releases. If you need
 * a stable logging format, use your own interceptor.
 */
public final class HttpLoggingInterceptor implements Interceptor {
	private static final Charset UTF8 = Charset.forName("UTF-8");

	public enum Level {
		/**
		 * No logs.
		 */
		NONE,
		/**
		 * Logs request and response lines.
		 * <p>
		 * <p>Example:
		 * <pre>{@code
		 * --> POST /greeting http/1.1 (3-byte body)
		 *
		 * <-- 200 OK (22ms, 6-byte body)
		 * }</pre>
		 */
		BASIC,
		/**
		 * Logs request and response lines and their respective headers.
		 * <p>
		 * <p>Example:
		 * <pre>{@code
		 * --> POST /greeting http/1.1
		 * Host: example.com
		 * Content-Type: plain/text
		 * Content-Length: 3
		 * --> END POST
		 *
		 * <-- 200 OK (22ms)
		 * Content-Type: plain/text
		 * Content-Length: 6
		 * <-- END HTTP
		 * }</pre>
		 */
		HEADERS,
		/**
		 * Logs request and response lines and their respective headers and bodies (if present).
		 * <p>
		 * <p>Example:
		 * <pre>{@code
		 * --> POST /greeting http/1.1
		 * Host: example.com
		 * Content-Type: plain/text
		 * Content-Length: 3
		 *
		 * Hi?
		 * --> END POST
		 *
		 * <-- 200 OK (22ms)
		 * Content-Type: plain/text
		 * Content-Length: 6
		 *
		 * Hello!
		 * <-- END HTTP
		 * }</pre>
		 */
		BODY
	}

	private static final Logger logger = Logger.getLogger(OkHttpClient.class.getName());
	public static final int INFO = 4;
	public static final int WARN = 5;

	public interface LLogger {
		void log(String message);
	}

	public HttpLoggingInterceptor() {
		this(new LLogger() {
			/**
			 * A {@link LLogger} defaults output appropriate for the current platform.
			 */
			@Override
			public void log(String message) {
				printLog(INFO, message, null);
//                Platform.get().log(INFO, message, null);
			}

			public void printLog(int level, String message, Throwable t) {
				java.util.logging.Level logLevel = level == WARN ? java.util.logging.Level.WARNING : java.util.logging.Level.INFO;
				logger.log(logLevel, message, t);
			}
		});
	}

	public HttpLoggingInterceptor(LLogger LLogger) {
		this.LLogger = LLogger;
	}

	private final LLogger LLogger;

	private volatile Level level = Level.NONE;

	/**
	 * Change the level at which this interceptor logs.
	 */
	public HttpLoggingInterceptor setLevel(Level level) {
		if (level == null) throw new NullPointerException("level == null. Use Level.NONE instead.");
		this.level = level;
		return this;
	}

	public Level getLevel() {
		return level;
	}

	@Override
	public Response intercept(Chain chain) throws IOException {
		Level level = this.level;
		LLogger.log("<--intercept----------------->");
		Request request = chain.request();
		if (level == Level.NONE) {
			return chain.proceed(request);
		}

		boolean logBody = level == Level.BODY;
		boolean logHeaders = logBody || level == Level.HEADERS;

		RequestBody requestBody = request.body();
		boolean hasRequestBody = requestBody != null;
		if (requestBody instanceof FormBody) {
			String requestParams = "---------------------request params-------------------------\n";
			FormBody formBody = ((FormBody) request.body());
			for (int i = 0; i < formBody.size(); i++) {
				requestParams += formBody.name(i) + "=" + formBody.value(i) + "\n";
			}
			requestParams += "---------------------request params-------------------------";
			LLogger.log(requestParams);
		}
		Connection connection = chain.connection();
		Protocol protocol = connection != null ? connection.protocol() : Protocol.HTTP_1_1;
		String requestStartMessage = "--> " + request.method() + ' ' + request.url() + ' ' + protocol;
		if (!logHeaders && hasRequestBody) {
			requestStartMessage += " (" + requestBody.contentLength() + "-byte body)";
		}
		LLogger.log(requestStartMessage);

		if (logHeaders) {
			if (hasRequestBody) {
				// Request body headers are only present when installed as a network interceptor. Force
				// them to be included (when available) so there values are known.
				if (requestBody.contentType() != null) {
					LLogger.log("Content-Type: " + requestBody.contentType());
				}
				if (requestBody.contentLength() != -1) {
					LLogger.log("Content-Length: " + requestBody.contentLength());
				}
			}

			Headers headers = request.headers();
			for (int i = 0, count = headers.size(); i < count; i++) {
				String name = headers.name(i);
				if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) {
					LLogger.log(name + ": " + headers.value(i));
				}
			}

			if (!logBody || !hasRequestBody) {
				LLogger.log("--> END " + request.method());
			} else if (bodyEncoded(request.headers())) {
				LLogger.log("--> END " + request.method() + " (encoded body omitted)");
			} else {
				Buffer buffer = new Buffer();
				requestBody.writeTo(buffer);

				Charset charset = UTF8;
				MediaType contentType = requestBody.contentType();
				if (contentType != null) {
					charset = contentType.charset(UTF8);
				}

			}
		}

		long startNs = System.nanoTime();
		Response response;
		try {
			response = chain.proceed(request);
		} catch (Exception e) {
			LLogger.log("<-- HTTP FAILED: " + e);
			throw e;
		}
		long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);

		ResponseBody responseBody = response.body();
		long contentLength = responseBody.contentLength();
		String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length";
		LLogger.log("<-- " + response.code() + ' ' + response.message() + ' '
				+ response.request().url() + " (" + tookMs + "ms" + (!logHeaders ? ", "
				+ bodySize + " body" : "") + ')');

		if (logHeaders) {
			Headers headers = response.headers();
			for (int i = 0, count = headers.size(); i < count; i++) {
				LLogger.log(headers.name(i) + ": " + headers.value(i));
			}

			if (!logBody || !HttpHeaders.hasBody(response)) {
				LLogger.log("<-- END HTTP");
			} else if (bodyEncoded(response.headers())) {
				LLogger.log("<-- END HTTP (encoded body omitted)");
			} else {
				BufferedSource source = responseBody.source();
				source.request(Long.MAX_VALUE); // Buffer the entire body.
				Buffer buffer = source.buffer();

				Charset charset = UTF8;
				MediaType contentType = responseBody.contentType();
				if (contentType != null) {
					try {
						charset = contentType.charset(UTF8);
					} catch (UnsupportedCharsetException e) {
						LLogger.log("");
						LLogger.log("Couldn't decode the response body; charset is likely malformed.");
						LLogger.log("<-- END HTTP");

						return response;
					}
				}

				if (!isPlaintext(buffer)) {
					LLogger.log("");
					LLogger.log("<-- END HTTP (binary " + buffer.size() + "-byte body omitted)");
					return response;
				}

				if (contentLength != 0) {
					LLogger.log("");
//                    LLogger.log(buffer.clone().readString(charset));
					json(buffer.clone().readString(charset));
				}

				LLogger.log("<-- END HTTP (" + buffer.size() + "-byte body)");
			}
		}

		return response;
	}

	/**
	 * Returns true if the body in question probably contains human readable text. Uses a small sample
	 * of code points to detect unicode control characters commonly used in binary file signatures.
	 */
	static boolean isPlaintext(Buffer buffer) {
		try {
			Buffer prefix = new Buffer();
			long byteCount = buffer.size() < 64 ? buffer.size() : 64;
			buffer.copyTo(prefix, 0, byteCount);
			for (int i = 0; i < 16; i++) {
				if (prefix.exhausted()) {
					break;
				}
				int codePoint = prefix.readUtf8CodePoint();
				if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
					return false;
				}
			}
			return true;
		} catch (EOFException e) {
			return false;
		}
	}

	private boolean bodyEncoded(Headers headers) {
		String contentEncoding = headers.get("Content-Encoding");
		return contentEncoding != null && !contentEncoding.equalsIgnoreCase("identity");
	}

	/**
	 * It is used for json pretty print
	 */
	private static final int JSON_INDENT = 2;

	private void json(String json) {
		if (LoggerHelper.isEmpty(json)) {
			LLogger.log("Empty/Null json content");
			return;
		}
		try {
			json = json.trim();
			if (json.startsWith("{")) {
				JSONObject jsonObject = new JSONObject(json);
				String message = jsonObject.toString(JSON_INDENT);
				d(message);
				return;
			}
			if (json.startsWith("[")) {
				JSONArray jsonArray = new JSONArray(json);
				String message = jsonArray.toString(JSON_INDENT);
				d(message);
				return;
			}
			LLogger.log("Invalid Json");
		} catch (JSONException e) {
			LLogger.log("Invalid Json");
			LLogger.log(json);
		}
	}

	//规定每段显示的长度
	private static int LOG_MAXLENGTH = 2000;

	public void d(Object object) {
		String message;
		if (object.getClass().isArray()) {
			message = Arrays.deepToString((Object[]) object);
		} else {
			message = object.toString();
		}
		int strLength = message.length();
		int start = 0;
		int end = LOG_MAXLENGTH;
		for (int i = 0; i < 100; i++) {
			//剩下的文本还是大于规定长度则继续重复截取并输出
			if (strLength > end) {
				LLogger.log(message.substring(start, end));
				start = end;
				end = end + LOG_MAXLENGTH;
			} else {
				LLogger.log(message.substring(start, strLength));
				break;
			}
		}

	}
}
